// DNI 48567659 ALBORS PONZO, JAIR

#include <iostream>
#include <stdlib.h>
#include <time.h>
#include <vector>

using namespace std;

// Contantes de texto (definidas por alumno)
const char kERROR_OP [] = "Error: Opcion incorrecta";
const char kERROR_OR [] = "Error: Orden incorrecta";
const char kERROR_MO [] = "Error: Movimiento incorrecto";
const char kAO_TABLERO[] = "   A B C D E F G H I J K L M N O"; // Letras que enmarcan tablero.
const int kNUM_LET = 26; // Nº Letras del abecedario.
const int kMAX_RACK = 7; // Nº Letras (máximo) del rack.

const char kFIN [] 	= "Final del juego";
const char kFIN_US [] = "Juego finalizado por el usuario.";

// Dimensiones del tablero
const int kFILAS=15;
const int kCOLUMNAS=15;

// Colores para el terminal. Ejemplo: cout << Azul << "Hola" << ResetColor;
const string Cyan="\e[46m";
const string Rosa="\e[45m\e[37m";
const string Azul="\e[44m\e[37m";
const string Rojo="\e[41m\e[37m";
const string ResetColor="\e[m";

// Orientacion de la palabra (si es horizontal o vertical)
enum Orientacion {HORIZONTAL, VERTICAL};

// Puntuaciones de las casillas en el tablero
enum Casilla {NORMAL,DOBLELETRA,TRIPLELETRA,DOBLEPALABRA,TRIPLEPALABRA};

// Array que indica la puntuación de cada una de las 26 letras, desde la A hasta la Z. Las letras con puntuacion=0
// no se usan porque no estan en la bolsa, por lo que no hay que hacer nada especial para este caso.
const int PUNTUACIONLETRAS[]={1,3,3,2,1,4,2,4,1,8,0,1,3,1,1,3,5,1,1,1,1,4,0,8,4,10}; 

// Array que indica la cantidad de letras que debe haber inicialmente en la bolsa (por ejemplo, 12 letras A pero solo una Z).
// Por tanto, la bolsa será un string que contiene "AAAAAAAAAAAABBCCCCC...".
const int NUMLETRAS[]={12,2,5,5,12,1,2,2,6,1,0,5,2,5,9,2,1,6,6,4,5,1,0,1,1,1}; // 97 piezas

// Array para imprimir un superindice por el terminal. Ejemplo para imprimir el numero 1: cout << SUPERINDEX[1];
const string SUPERINDEX[]={"\u2070","\u00B9","\u00B2","\u00B3","\u2074","\u2075","\u2076","\u2077","\u2078","\u2079"};

// Caracter para indicar que una casilla no tiene ninguna letra
const char CASILLAVACIA=' ';

// En esta practica, veras que en las cabeceras de las funciones del enunciado se usa Letra en lugar de char para las letras
// del tablero, la bolsa y el rack. Es lo mismo que char, pero llamarlo Letra te ayudara para las siguientes practicas, 
// ya que en estas Letra tendra mas elementos y asi podras reutilizar las funciones de la practica 1
typedef char Letra;

// Definicion del tablero
typedef Casilla Tablero[kFILAS][kCOLUMNAS];

// En esta practica, el Rack sera un string. Al igual que el tipo Letra, poner Rack en lugar de string te ayudara
// para las siguientes practicas, ya que en estas el Rack tendra mas elementos y asi podras reutilizar las funciones de la 
// practica 1
typedef string Rack;

// Registros
struct Coordenada
{
	int fila;
	int columna;
};

struct Movimiento
{
	Coordenada origen;
	Orientacion orientacion;
	string palabra;
};

struct Partida 
{
	Tablero tablero;
	int numJugadores; // Cantidad de jugadores
	int turno; // Turno (numero) del jugador actual

	Letra letras[kFILAS][kCOLUMNAS]; // Matriz de letras que se colocan sobre el tablero
	string bolsa; // Elementos de la bolsa
	vector<Rack> racks; // Vector de racks. Para cambiar su tamanyo a 4 elementos, por ejemplo: racks.resize(4);
};

/***********************************************************************************************************/

// IMPORTANTE: Esta funcion debe llamarse una sola vez por partida (cuando esta se crea), y para que el autocorrector 
// funcione correctamente no debe modificarse ni borrarse
void inicializarSemilla()
{
	srand(time(NULL));
}

// Funcion para inicializar el tablero
void inicializarTablero(Tablero &tablero)
{
    const Casilla inicializacionTablero[8][8] = {
    	{TRIPLEPALABRA, NORMAL,       NORMAL,       DOBLELETRA,   NORMAL,       NORMAL,      NORMAL,     TRIPLEPALABRA}, 
    	{NORMAL,        DOBLEPALABRA, NORMAL,       NORMAL,       NORMAL,       TRIPLELETRA, NORMAL,     NORMAL}, 
    	{NORMAL,        NORMAL,       DOBLEPALABRA, NORMAL,       NORMAL,       NORMAL,      DOBLELETRA, NORMAL}, 
    	{DOBLELETRA,    NORMAL,       NORMAL,       DOBLEPALABRA, NORMAL,       NORMAL,      NORMAL,     DOBLELETRA}, 
    	{NORMAL,        NORMAL,       NORMAL,       NORMAL,       DOBLEPALABRA, NORMAL,      NORMAL,     NORMAL}, 
    	{NORMAL,        TRIPLELETRA,  NORMAL,       NORMAL,       NORMAL,       TRIPLELETRA, NORMAL,     NORMAL}, 
	{NORMAL,        NORMAL,       DOBLELETRA,   NORMAL,       NORMAL,       NORMAL,      DOBLELETRA, NORMAL}, 
	{TRIPLEPALABRA, NORMAL,       NORMAL,       DOBLELETRA,   NORMAL,       NORMAL,      NORMAL,     DOBLEPALABRA}
    };

    for (int i=0; i<kFILAS; i++) 
    {
    	for (int j=0; j<kCOLUMNAS; j++) 
    	{
		int fila = (i<= kFILAS / 2 ? i : kFILAS - i - 1 );
		int columna = (j<= kCOLUMNAS / 2 ? j : kCOLUMNAS - j - 1 );
		
		tablero[i][j] = inicializacionTablero[fila][columna];
	}
    }
}

// Cabeceras de las funciones que deben implementarse. No hace falta que esten declarados los prototipos, 
// pero si que deben estar implementadas con estos nombres y parametros

// bool sacarLetraRack(Letra l, Rack &rack); // *Nota: Se aprovecha recorridos for de comprobar rack.
bool comprobarMovimiento(Movimiento m, Rack &rack);
bool completarRack(string &bolsa, Rack &rack);
Coordenada leerCoordenada(string coord, Orientacion &orientacion);
Movimiento leerMovimiento(string orden);
Partida inicializarPartida(int numJugadores);
string imprimeLetra(char l);
void margenTablero(Movimiento &m);
void imprimirTablero(Tablero &tablero, Letra letras[][kCOLUMNAS]);
void imprimirRack(Rack r);
void jugar(Partida &p);
void comenzarPartida();
void inicializarBolsa(Partida &partida);
void inicializarLetras(Partida &partida);
void inicializarRacks (Partida &partida);
void guardarPartida(void);
void mostrarAyuda(void);
void guardarMovimiento(Partida &p, Movimiento m);
void MinusToMayus(string &palabra);


int main()
{
  char SeleccionMenu;
  
  // Menú principal, se ejecuta mientras selección sea distinto de q.
  do
  {
    cout << "--------------------------------" << endl;
    cout << "---- Scrabble Programacion 2 ---" << endl;
    cout << "--------------------------------" << endl;
    cout << "1- Nueva partida\n2- Cargar partida\nq- Salir" << endl;
    cout << "Opcion: ";
    cin >> SeleccionMenu;
      cin.get(); // Limpiamos buffer.
    
    switch (SeleccionMenu)
    {
      case '1': comenzarPartida();
	break;
	
      case '2': //Cargar partida
	break;
	
      case 'q': //cout << kFIN_US << endl;
	break;
	
      default: cout << kERROR_OP << endl;
    }
  }
  while (SeleccionMenu != 'q');
}

//---------------------------------- COMENZAR PARTIDA-----------------------------------//
// En este módulo pedimos número de jugadores e inicializamos la partida.

void comenzarPartida()
{
  int numJugadores; // Nº de jugadores, se pide al usuario.
  Partida partida; //----> Retorna desde inicializarPartida(numJugadores)
  
  // Validamos que sea correcto.
  do
  {
    cout << "Numero de jugadores (1-4): ";
    cin >> numJugadores;
      cin.get(); // Limpiamos buffer.

    //if(numJugadores<1 || numJugadores>4)
      //cout << kERROR_OP << endl;
  }
  while (numJugadores<1 || numJugadores>4);
  
  // Llamamos a inicializarPartida pasándole cantidad de jugadores.
  partida = inicializarPartida(numJugadores);
  
  // Llamamos a Jugar.
  jugar(partida);
}

//-------------------------------- INICIALIZAR PARTIDA----------------------------------//
// Inicializamos las componentes de juego.

Partida inicializarPartida(int numJugadores)
{
  Partida partida;
  
  partida.numJugadores = numJugadores;
  inicializarSemilla();
  inicializarBolsa(partida); // Inicializamos bolsa de letras.
  inicializarLetras(partida);
  partida.turno = 0; // Inicializamos turno en jugador 0.
  inicializarTablero(partida.tablero);
  partida.racks.resize(partida.numJugadores);

  return(partida);
}

//----------------------------------- JUGAR PARTIDA-------------------------------------//
// Cuerpo del juego.

void jugar(Partida &p)
{
  string orden;
  bool juegoTerminado = false; // Marcador para activar el fin de juego.
  
  do
  {
    p.turno = p.turno % p.numJugadores;
    
    if(p.bolsa.length()==0 && p.racks[p.turno].size()==0) // Si la bolsa está vacía y se termina
							  // mensaje de fin de juego y salir.
    {
      juegoTerminado = true;
      cout << kFIN << endl;
    }
    
    else
    { 
      imprimirTablero(p.tablero,p.letras);		// Imprime tablero.
      
      cout << "Letras jugador " << p.turno+1 << ": "; 	// Imprimir turno jugador
      
      inicializarRacks(p);
      imprimirRack(p.racks[p.turno]);		   	// y su rack.
      
      cout << "Palabra (h para ayuda): ";
      getline(cin,orden);

      if(orden.length()==1) 	// Este if divide la orden, si es de tamaño
      {				// >1 se entiende que es un movimiento de juego.
	switch(orden[0])
	{
	  case 'h':mostrarAyuda();
	    break;
	  case 'g':guardarPartida();
	    break;
	}
      }
	
      else
      {
	// ----------- JUEGO -------------------//

	    Movimiento m;
	    m = leerMovimiento(orden); // Asignamos a movimiento el resultado de procesar 'orden'.

	    if(m.origen.fila != -1 || m.origen.columna != -1)
	    {
	    margenTablero(m); // Comprueba que no te salgas del tablero.
	    
	      if(comprobarMovimiento(m, p.racks[p.turno]))
	      {
		guardarMovimiento(p,m);
		completarRack(p.bolsa, p.racks[p.turno]); // Se rellena el rack.
		
		   if(!(p.bolsa.length()==0 && p.racks[p.turno].size()==0))
		     p.turno++; // Sólo incrementa turno si no se reconoce fin de juego.
	      }
	      else
		cout << kERROR_MO << endl; // Mensaje Error: Movimiento incorrecto.
	    }  
	    else
	      cout << kERROR_OR << endl;  // Mensaje Error: Orden incorrecta.
      }
    }
  } 
  while (orden != "q" && juegoTerminado==false); 
  // Se ejecuta hasta que el jugador quiera salir (q) o se termine el juego.
}


//-------------------------------- INICIALIZAR BOLSA------------------------------------//
// Genera la bolsa desde A hasta Z poniendo de cada una la cantidad de NUMLETRAS[i]

void inicializarBolsa(Partida &partida)
{
  int i,j, k;
  char letra = 'A'; // Inicializamos variable en la primera letra.

  
  for(i=0,k=0;i<kNUM_LET;i++) // kNUM_LET = 26 (int).
  {
    for(j=0;j<NUMLETRAS[i];j++,k++)
    {
      partida.bolsa += (char)letra;
    }
    (char)(letra++);
  }
}

//-------------------------------- MOSTRAR AYUDA----------------------------------------//
void mostrarAyuda(void)
{
  cout << "Ejemplo de movimiento: XY PALABRA" << endl;
  cout << "Otras opciones:" << endl;
  cout << " h: Ayuda" << endl;
  cout << " g: Guardar partida" << endl;
  cout << " q: Salir de la partida" << endl;

}

//-------------------------------- GUARDAR PARTIDA--------------------------------------//
void guardarPartida(void) //pasar parametros para guardar
{
  // Guardar();
}

//-------------------------------- IMPRIMIR TABLERO-------------------------------------//
// Recorre matriz tablero imprimiendo letras y colores correspondientes.

void imprimirTablero(Tablero &tablero, Letra letras[][kCOLUMNAS])
{
  int i,j;
  
  cout << kAO_TABLERO << endl; // Letras sobre tablero.
 
  for(i=0; i<kFILAS;i++)
  {
    cout << i+1 << CASILLAVACIA;
    if(i<9)
      cout << CASILLAVACIA; // espacios que siguen de 1-9.
    
    for(j=0;j<kCOLUMNAS;j++)
    {
      switch(tablero[i][j])
      {
	case TRIPLEPALABRA:
	  cout << Rojo << imprimeLetra(letras[i][j]) << ResetColor;
	  break;
      
	case DOBLEPALABRA:
	  cout << Rosa << imprimeLetra(letras[i][j]) << ResetColor;
	  break;

	case DOBLELETRA:
	  cout << Cyan << imprimeLetra(letras[i][j]) << ResetColor;
	  break;

	case TRIPLELETRA:
	  cout << Azul << imprimeLetra(letras[i][j]) << ResetColor;
	  break;

	case NORMAL:
	  cout  << imprimeLetra(letras[i][j]);
	  break;
      }
    }
  cout << CASILLAVACIA << i+1;
  cout << endl;  
  }
  cout << kAO_TABLERO << endl; 
}

//-------------------------------- IMPRIMIR LETRA---------------------------------------//
// devuelve un string de letra += superíndice correspondiente.

string imprimeLetra(char l)
{
    string aux;
    
    if (l != CASILLAVACIA)
    {
      if(l != 'Z')
      {
	aux =  l;
	int i = PUNTUACIONLETRAS[l-'A']; // el superindice
	aux += SUPERINDEX[i];
      }
      else // caso especial 'Z'
      {
	aux = l;
	aux += SUPERINDEX[0];
      }
    }
    else
    {
      aux = CASILLAVACIA;
      aux += CASILLAVACIA;
    }
    
    return aux;
}

//---------------------------------- IMPRIMIR RACK--------------------------------------//
// recorre e imprime rack de turno actual.

void imprimirRack(Rack r)
{  
  for(int i=0;i<(int)r.length();i++)
  {
    cout << r[i];
    if(r[i] != 'Z')
      cout << SUPERINDEX[PUNTUACIONLETRAS[(int)r[i]-'A']];
    else
      cout << SUPERINDEX[0]; // Si es Z superíndice 0.
  }
  cout << endl;
}

//------------------------------- INICIALIZAR LETRAS------------------------------------//
// Inicializamos matriz de letras a casilla vacía.

void inicializarLetras (Partida &partida)
{
  int i,j;
  
  for(i=0;i<kFILAS;i++)
  {
    for(j=0;j<kCOLUMNAS;j++)
    {
      partida.letras[i][j] = CASILLAVACIA;
    }
  }  
}

//------------------------------- INICIALIZAR RACKS ------------------------------------//
// Inicializamos racks de jugadores (luego completar rack actualizará en cada turno).

void inicializarRacks (Partida &partida)
{
  for(int i=0; i<partida.numJugadores;i++) // Itera numJugadores veces
  {
      completarRack(partida.bolsa, partida.racks[i]);
  }
}

//------------------------------- LEER MOVIMIENTO --------------------------------------//
// Divide movimiento y coordenada.

Movimiento leerMovimiento(string orden)
{
  int blanco = orden.find(" ",0);  // Localiza el espacio.
  Movimiento m; 
  
    string coordenada = orden.substr(0,blanco); // Separa la coordenada.
    m.palabra = orden.substr(blanco+1,orden.length()); // Separa palabra.
    MinusToMayus(m.palabra); // Convierte a mayúscula cualquier minúscula que exista en palabra.
    
  m.origen = leerCoordenada(coordenada,m.orientacion); // sale Coordenada orígen y orientación.
  return m;
}

//------------------------------- LEER COORDENADA --------------------------------------//
// Distingue si la coordenada es de 2 cifras (números 1-9+ letra) o de 3 cifras (1-15+letra).

Coordenada leerCoordenada(string coord, Orientacion &orientacion)
{ 
  Coordenada c;

  switch (coord.length())
  { 
    case 2: // Coordenada de 2 cifras.
      
      if((coord[0] >='A') && (coord[0] <='O') && coord[1]!='0') // empieza por letra
      {
	orientacion = VERTICAL;
	c.columna = coord[0]-'A';
	coord.erase(0,1);
	c.fila = atoi(coord.c_str())-1;
      }
      
      else if((coord[0] >='1') && (coord[0] <='9')) // empieza por numero
      {
	orientacion = HORIZONTAL;
	c.columna = coord[1]-'A';
	c.fila = atoi(coord.c_str())-1;
      }
      
      else //caracter no admitido
      {
	c.fila		= -1;
	c.columna	= -1;
      }
      
	break;

    case 3: // Coordenada de 3 cifras, también comprueba que sea coherente cada una de los 3 chars.
      
      if((coord[0] >='A') && (coord[0] <='O') && // empieza por letra
	(coord[1] >='1') && (coord[1] <='9') &&  // la primera cifra del número no es 0.
	(coord[2] >='0') && (coord[2] <='5'))    // la segunda cifra no es 0 ni sobrepasa 5.
      {
	orientacion = VERTICAL;
	c.columna = coord[0]-'A';
	coord.erase(0,1);
	c.fila = atoi(coord.c_str())-1;
      }
      
      else if ((coord[0] >='1') && (coord[0] <='9') &&
	      (coord[1] >='0') && (coord[1] <='5') &&
	      (coord[2] >='A') && (coord[2] <='O'))// empieza por numero
      {
	orientacion = HORIZONTAL;
	c.columna = coord[coord.size()-1]-'A';
	c.fila = atoi(coord.c_str())-1;
      }
      
      else //caracter no admitido
      {
	c.fila		= -1;
	c.columna	= -1;
      }
	
	break;
  }
  
  return c;
}

//---------------------------- COMPROBAR MOVIMIENTO ------------------------------------//
// Comprueba movimiento y aprovecha el bucle para sacar letra de rack siempre que se acepte el movimiento.

bool comprobarMovimiento(Movimiento m, Rack &rack)
{
  int contador_letras = 0;
  string auxr = rack; //--------- String auxiliar para borrar letras.
  string auxp = m.palabra; //---- String auxiliar para borrar palabra.
	  
  for(int j=0;j<(int)auxp.length();j++) //--recorre palabra de jugador
  {
    bool encontrado = false; //--- Para condición de salida del bucle.
    
      for(int i=0; i<(int)auxr.length() && !encontrado; i++) //-recorre rack
      {
	if(auxp[j] == auxr[i]) // Si existe coincidencia, borra y sigue comprobando.
	{ 
	  auxr.erase(i,1);
	  contador_letras += 1;
	  encontrado  = true;
	}
      }
  }
  
  if((int)auxp.length() == contador_letras)
    rack = auxr; // Si todas las comprobaciones han sido satisfactorias asignamos auxiliar a rack.
  
  return ((int)auxp.length() == contador_letras);
	// Si es true significa que todas las letras estaban en su rack.
}

//---------------------------- COMPLETAR RACK ------------------------------------------//
// Completa rack de turno hasta que la bolsa se vacíe.

bool completarRack(string &bolsa, Rack &rack)
{
  bool sinFichas = false; // Marcador para fichas agotadas.
  
   for(int i=rack.length(); i<kMAX_RACK  &&  bolsa.length()!=0 ;i++) // Itera 7 veces
   {  
      int n = rand()% bolsa.length();
	  
      rack.push_back(bolsa[n]); // rack += partida.bolsa[n];
	      
      bolsa.erase(n,1); // borrar de partida.bolsa cada letra que se ponga en rack.
   }
   
   if(bolsa.length() == 0 && rack.length()==0)
     sinFichas = true;
   
   return sinFichas;
   // Si vuelve como true ha ganado el jugador de este turno
   // ya que se ha quedado sin fichas y no quedan más en la bolsa.
}

//---------------------------- GUARDAR MOVIMIENTO --------------------------------------//
// Guarda movimiento en tablero dependiendo de su orientación.

void guardarMovimiento(Partida &p, Movimiento m)
{
  int fila,columna,k;
  

    for(fila=0; fila<(int)kFILAS; fila++)
    {
      for(columna=0; columna<(int)kCOLUMNAS; columna++)
      {
	if(m.origen.fila == fila  &&  m.origen.columna == columna) // Posición de inicio.
	{
	    for(k=0; k<(int)m.palabra.length(); k++)
	    {
	      if(m.orientacion == HORIZONTAL)
		p.letras[fila][columna+k] = m.palabra[k];
	      
	      else //if(m.orientacion == VERTICAL)
		p.letras[fila+k][columna] = m.palabra[k];
	    }
	}
      }
    }
}

//---------------------------- CONVERTIR A MAYUSCULA --------------------------------------//
// Convierte minúsculas a mayúsculas.

void MinusToMayus(string &palabra)
{
  for(int i=0;i<(int)palabra.length();i++)
  {
    if(palabra[i]>='a') // 97 (ascii) en adelante
    {
      palabra[i]=(palabra[i]-'a')+'A';
    }
  }
}

//---------------------------- CONTROLADOR MÁRGENES --------------------------------------//
// Convierte minúsculas a mayúsculas.
// (if)si casilla de inicio + largo de palabra sale del margen...

void margenTablero(Movimiento &m)
{
  if(m.orientacion == HORIZONTAL)
  {
    if(m.origen.columna + (int)m.palabra.length() > kCOLUMNAS) 
    {
      m.origen.fila = -1;
      m.origen.columna = -1; 
    }
  }
  
  else // (m.orientacion == VERTICAL)
  {
    if((m.origen.fila+1) + m.palabra.length() > kFILAS+1)
    {
      m.origen.fila = -1;
      m.origen.columna = -1;
    }
  }
}